import java.io.*;
import java.util.Scanner;

/**
 * Exception Handling Examples
 * This class demonstrates various exception handling techniques in Java
 */
public class ExceptionHandling {
    
    public static void main(String[] args) {
        ExceptionHandling example = new ExceptionHandling();
        
        // Demonstrating exception overview
        example.demonstrateExceptionOverview();
        
        // Demonstrating try-catch-finally
        example.demonstrateTryCatchFinally();
        
        // Demonstrating throws and throw keywords
        example.demonstrateThrowsAndThrow();
        
        // Demonstrating custom exceptions
        example.demonstrateCustomExceptions();
        
        // Demonstrating exception chaining
        example.demonstrateExceptionChaining();
        
        // Demonstrating try-with-resources (Java 7+)
        example.demonstrateTryWithResources();
    }
    
    /**
     * Method to demonstrate exception overview
     */
    public void demonstrateExceptionOverview() {
        System.out.println("=== Exception Overview ===");
        
        // Demonstrating common exception types
        try {
            // ArithmeticException
            int result = 10 / 0;
        } catch (ArithmeticException e) {
            System.out.println("Caught ArithmeticException: " + e.getMessage());
        }
        
        try {
            // ArrayIndexOutOfBoundsException
            int[] arr = new int[5];
            int value = arr[10];
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Caught ArrayIndexOutOfBoundsException: " + e.getMessage());
        }
        
        try {
            // NullPointerException
            String str = null;
            int length = str.length();
        } catch (NullPointerException e) {
            System.out.println("Caught NullPointerException: " + e.getMessage());
        }
        
        System.out.println("Throwable class hierarchy:");
        System.out.println("- Throwable (root class)");
        System.out.println("  - Error (unchecked)");
        System.out.println("    - OutOfMemoryError");
        System.out.println("    - StackOverflowError");
        System.out.println("  - Exception (checked and unchecked)");
        System.out.println("    - RuntimeException (unchecked)");
        System.out.println("      - IllegalArgumentException");
        System.out.println("      - NullPointerException");
        System.out.println("      - ArithmeticException");
        System.out.println("    - IOException (checked)");
        System.out.println("    - SQLException (checked)");
        System.out.println();
    }
    
    /**
     * Method to demonstrate try-catch-finally statement blocks
     */
    public void demonstrateTryCatchFinally() {
        System.out.println("=== Try-Catch-Finally ===");
        
        // Basic try-catch
        try {
            int result = 10 / 0;
        } catch (ArithmeticException e) {
            System.out.println("Caught exception in basic try-catch: " + e.getMessage());
        }
        
        // Multiple catch blocks
        try {
            String str = null;
            int len = str.length(); // This will throw NullPointerException
            int result = 10 / 0;    // This would throw ArithmeticException
        } catch (NullPointerException e) {
            System.out.println("Caught NullPointerException: " + e.getMessage());
        } catch (ArithmeticException e) {
            System.out.println("Caught ArithmeticException: " + e.getMessage());
        }
        
        // Catch with multi-catch (Java 7+)
        try {
            String input = null;
            int num = Integer.parseInt(input); // NumberFormatException
            int result = 10 / num;             // ArithmeticException
        } catch (NumberFormatException | ArithmeticException e) {
            System.out.println("Caught NumberFormatException or ArithmeticException: " + e.getMessage());
        }
        
        // Try-catch-finally
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("nonexistent.txt");
        } catch (FileNotFoundException e) {
            System.out.println("Caught FileNotFoundException: " + e.getMessage());
        } finally {
            System.out.println("Finally block executed");
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    System.out.println("Error closing file: " + e.getMessage());
                }
            }
        }
        System.out.println();
    }
    
    /**
     * Method to demonstrate throws and throw keywords
     */
    public void demonstrateThrowsAndThrow() {
        System.out.println("=== Throws and Throw Keywords ===");
        
        // Demonstrating throw keyword
        try {
            validateAge(-5);
        } catch (IllegalArgumentException e) {
            System.out.println("Caught exception from validateAge: " + e.getMessage());
        }
        
        // Demonstrating throws keyword
        try {
            divide(10, 0);
        } catch (ArithmeticException e) {
            System.out.println("Caught exception from divide method: " + e.getMessage());
        }
        
        System.out.println();
    }
    
    // Method that throws an exception
    public int divide(int a, int b) throws ArithmeticException {
        if (b == 0) {
            throw new ArithmeticException("Division by zero is not allowed");
        }
        return a / b;
    }
    
    // Method that throws an exception manually
    public void validateAge(int age) throws IllegalArgumentException {
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative");
        }
        System.out.println("Valid age: " + age);
    }
    
    /**
     * Method to demonstrate custom exceptions
     */
    public void demonstrateCustomExceptions() {
        System.out.println("=== Custom Exceptions ===");
        
        try {
            checkBalance(500, 1000);
        } catch (InsufficientFundsException e) {
            System.out.println("Caught custom exception: " + e.getMessage());
            System.out.println("Deficit: " + e.getAmount());
        }
        
        try {
            validateEmail("invalid-email");
        } catch (InvalidEmailException e) {
            System.out.println("Caught custom exception: " + e.getMessage());
        }
        
        System.out.println();
    }
    
    // Method that throws a custom exception
    public void checkBalance(double balance, double withdrawal) throws InsufficientFundsException {
        if (withdrawal > balance) {
            throw new InsufficientFundsException(withdrawal - balance);
        }
        System.out.println("Withdrawal successful. Remaining balance: " + (balance - withdrawal));
    }
    
    // Method that throws another custom exception
    public void validateEmail(String email) throws InvalidEmailException {
        if (!email.contains("@")) {
            throw new InvalidEmailException("Email must contain @ symbol: " + email);
        }
        System.out.println("Valid email: " + email);
    }
    
    /**
     * Method to demonstrate exception chaining
     */
    public void demonstrateExceptionChaining() {
        System.out.println("=== Exception Chaining ===");
        
        try {
            processFile("data.txt");
        } catch (FileProcessingException e) {
            System.out.println("Caught FileProcessingException: " + e.getMessage());
            System.out.println("Caused by: " + e.getCause().getClass().getSimpleName() + 
                              " - " + e.getCause().getMessage());
        }
        
        System.out.println();
    }
    
    public void processFile(String filename) throws FileProcessingException {
        try {
            FileReader file = new FileReader(filename);
            // Process file...
        } catch (FileNotFoundException e) {
            // Chain the FileNotFoundException to our custom exception
            throw new FileProcessingException("Error processing file: " + filename, e);
        }
    }
    
    /**
     * Method to demonstrate try-with-resources statement (Java 7+)
     */
    public void demonstrateTryWithResources() {
        System.out.println("=== Try-With-Resources ===");
        
        // Old way - manually closing resources
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("example.txt");
            // Process file...
        } catch (FileNotFoundException e) {
            System.out.println("File not found (old way): " + e.getMessage());
        } finally {
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    System.out.println("Error closing file (old way): " + e.getMessage());
                }
            }
        }
        
        // New way - try-with-resources (Java 7+)
        try (FileInputStream fis2 = new FileInputStream("example.txt")) {
            // Process file...
            System.out.println("File processed successfully with try-with-resources");
        } catch (FileNotFoundException e) {
            System.out.println("File not found (try-with-resources): " + e.getMessage());
        } catch (IOException e) {
            System.out.println("IO error (try-with-resources): " + e.getMessage());
        }
        // Resource is automatically closed
        
        // Multiple resources in try-with-resources
        try (FileInputStream input = new FileInputStream("input.txt");
             FileOutputStream output = new FileOutputStream("output.txt")) {
            
            // Process files...
            System.out.println("Multiple resources handled with try-with-resources");
            
        } catch (IOException e) {
            System.out.println("IO error with multiple resources: " + e.getMessage());
        }
        // Both resources are automatically closed
        
        System.out.println();
    }
}

// Custom exception class for insufficient funds
class InsufficientFundsException extends Exception {
    private double amount;
    
    public InsufficientFundsException(double amount) {
        super("Insufficient funds in account");
        this.amount = amount;
    }
    
    public double getAmount() {
        return amount;
    }
}

// Custom exception class for invalid email
class InvalidEmailException extends Exception {
    public InvalidEmailException(String message) {
        super(message);
    }
}

// Custom exception class demonstrating exception chaining
class FileProcessingException extends Exception {
    public FileProcessingException(String message, Throwable cause) {
        super(message, cause);
    }
}